﻿using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Ukagaka.Named.Ghost.SakuraScript
{
	/// <summary>
	/// SakuraScriptパーサを表現します
	/// </summary>
	public class Parser : IEnumerable<Element>
	{
		/// <summary>
		/// SakuraScriptを取得します
		/// </summary>
		public string Script
		{
			get;
			private set;
		}

		/// <summary>
		/// SakuraScriptパーサを表現するインスタンスを作成します
		/// </summary>
		/// <param name="script">SakuraScript</param>
		public Parser( string script )
		{
			Script = script;
		}

		#region 解析処理 ...

		/// <summary>
		/// SakuraScriptの発言部のパース結果を取得します
		/// </summary>
		/// <param name="seeker">SakuraScirptの移動参照</param>
		/// <returns>SakuraScriptの発言部</returns>
		private Talk Parsing_Talk( StringSeeker seeker )
		{
			var sb = new StringBuilder( Script.Length );

			while ( !seeker.EndOfSource )
			{
				var nc = ( char )seeker.GetSeekChar( );

				if ( Markup.IsLeader( nc ) && !Markup.IsLeader( ( char )seeker.GetNextChar( ) ) )
				{
					if ( 0 < sb.Length )
					{
						return new Talk( sb.ToString( ) );
					}
					else
					{
						return null;
					}
				}
				else
				{
					sb.Append( nc );
				}

				seeker.MoveNext( );
			}

			if ( 0 < sb.Length )
			{
				return new Talk( sb.ToString( ) );
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// SakuraScriptのコマンド部のパース結果を取得します
		/// </summary>
		/// <param name="seeker">SakuraScirptの移動参照</param>
		/// <returns>SakuraScriptのコマンド部</returns>
		private Command Parsing_Command( StringSeeker seeker )
		{
			var markup = Parsing_Markup( seeker );

			if ( null == markup )
			{
				return null;
			}

			var command = new Command( markup );
			var parameter = ( Parameter )null;

			while ( null != ( Parsing_Parameter( seeker ) ) )
			{
				command.Parameters.Add( parameter );
			}

			return command;
		}

		/// <summary>
		/// SakuraScriptのマークアップ部のパース結果を取得します
		/// </summary>
		/// <param name="seeker">SakuraScirptの移動参照</param>
		/// <returns>SakuraScriptのマークアップ部</returns>
		private Markup Parsing_Markup( StringSeeker seeker )
		{
			var sb = new StringBuilder( Script.Length );

			{
				var nc = ( char )seeker.GetSeekChar( );

				if ( Markup.IsLeader( nc ) && seeker.MoveNext( ) )
				{
					// through
				}
				else
				{
					return null;
				}

				sb.Append( nc );
			}

			while ( !seeker.EndOfSource )
			{
				var nc = ( char )seeker.GetSeekChar( );

				if ( Markup.IsLeader( nc ) || '[' == nc )
				{
					if ( 0 < sb.Length )
					{
						return new Markup( sb.ToString( ) );
					}
				}
				else if ( Markup.IsMarkupChar( nc ) )
				{
					sb.Append( nc );
				}
				else
				{
					break;
				}

				seeker.MoveNext( );
			}

			if ( 0 < sb.Length )
			{
				return new Markup( sb.ToString( ) );
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// SakuraScriptのパラメータ部のパース結果を取得します
		/// </summary>
		/// <param name="seeker">SakuraScirptの移動参照</param>
		/// <returns>SakuraScriptのパラメータ部</returns>
		private Parameter Parsing_Parameter( StringSeeker seeker )
		{
			var sb = new StringBuilder( Script.Length );

			{
				var nc = ( char )seeker.GetSeekChar( );

				if ( '[' == nc && seeker.MoveNext( ) )
				{
					// through
				}
				else
				{
					return null;
				}
			}

			while ( !seeker.EndOfSource )
			{
				var nc = ( char )seeker.GetSeekChar( );

				if ( ']' == nc )
				{
					seeker.MoveNext( );

					if ( 0 < sb.Length )
					{
						return new Parameter( sb.ToString( ) );
					}
					else
					{
						return null;
					}
				}
				else
				{
					sb.Append( nc );
				}

				seeker.MoveNext( );
			}

			if ( 0 < sb.Length )
			{
				return new Parameter( sb.ToString( ) );
			}
			else
			{
				return null;
			}
		}

		#endregion

		#region IEnumerable<Element> メンバー

		/// <summary>
		/// エレメントを反復処理する列挙子を取得します
		/// </summary>
		/// <returns>エレメントを反復処理する列挙子</returns>
		public IEnumerator<Element> GetEnumerator( )
		{
			var seeker = new StringSeeker( Script );
			var element = ( Element )null;

			while ( !seeker.EndOfSource )
			{
				while ( null != ( element = Parsing_Command( seeker ) ) )
				{
					yield return element;
				}

				while ( null != ( element = Parsing_Talk( seeker ) ) )
				{
					yield return element;
				}
			}
		}

		#endregion

		#region IEnumerable メンバー

		/// <summary>
		/// エレメントを反復処理する列挙子を取得します
		/// </summary>
		/// <returns>エレメントを反復処理する列挙子</returns>
		IEnumerator IEnumerable.GetEnumerator( )
		{
			return GetEnumerator( );
		}

		#endregion
	}
}
